﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.CompilerServices;
using System.IO;
using System.Diagnostics;
using System.Windows.Forms;

namespace LowLevelGraphics.Filter
{
    /// <summary>
    /// Use this to find a bitmap in another bitmap
    /// </summary>
    public static class BitmapFinder
    {
        private const int BytesPerPixel = 3;

        /// <summary>
        /// Finds the bitmap in bitmap.
        /// </summary>
        /// <param name="bitmap">The bitmap.</param>
        public static void FindBitmapInBitmap(Bitmap bitmap)
        {
            Bitmap Target = bitmap;
            Rectangle ScreenBounds = Screen.PrimaryScreen.Bounds;
            Bitmap Source = new Bitmap(ScreenBounds.Width, ScreenBounds.Height, PixelFormat.Format32bppArgb);
            using (Graphics Gfx = Graphics.FromImage(Source))
            {
                Gfx.CopyFromScreen(Point.Empty, Point.Empty, ScreenBounds.Size);
            }
            Rectangle? Result = Source.FindBitmap(Target);
            Debug.WriteLine(Result.HasValue ? string.Format("Found bitmap at: {0}", Result.Value.ToString()) : "Could not find bitmap.");
        }

        /// <summary>
        /// Finds the bitmap.
        /// </summary>
        /// <param name="Source">The source.</param>
        /// <param name="Target">The target.</param>
        /// <returns></returns>
        public static Rectangle? FindBitmap(this Bitmap Source, Bitmap Target)
        {
            return Source.FindBitmap(new Rectangle(Point.Empty, Source.Size), Target);
        }

        /// <summary>
        /// Compares the byte array.
        /// </summary>
        /// <param name="Array1">The array1.</param>
        /// <param name="Array2">The array2.</param>
        /// <returns></returns>
        private static bool CompareByteArray(byte[] Array1, byte[] Array2)
        {
            for (int Position = 0; Position <= Array1.Length - 1; Position++)
            {
                if (Array1[Position] != Array2[Position]) return false;
            }
            return true;
        }

        /// <summary>
        /// Finds the bitmap.
        /// </summary>
        /// <param name="Source">The source.</param>
        /// <param name="Area">The area.</param>
        /// <param name="Target">The target.</param>
        /// <returns></returns>
        public static Rectangle? FindBitmap(this Bitmap Source, Rectangle Area, Bitmap Target)
        {
            BitmapData SourceData = null;
            byte[] SourceBytes = Source.GetBitmapData(new Rectangle(0, 0, Source.Width, Source.Height), ref SourceData);

            BitmapData TargetData = null;
            byte[] TargetBytes = Target.GetBitmapData(new Rectangle(0, 0, Target.Width, Target.Height), ref TargetData);

            Color FirstTargetPixel = TargetBytes.GetPixelFromArray(Point.Empty, TargetData);
            byte[] TargetSection = TargetBytes.GetSection(new Rectangle(0, 0, Target.Width, Target.Height), TargetData);

            for (int Y = Area.Top; Y <= Area.Bottom - Target.Height; Y++)
            {
                for (int X = Area.Left; X <= Area.Right - Target.Width; X++)
                {
                    if (!SourceBytes.GetPixelFromArray(new Point(X, Y), SourceData).ToArgb().Equals(FirstTargetPixel)) continue;
                    Rectangle SectionBounds = new Rectangle(X, Y, Target.Width, Target.Height);
                    byte[] Section = SourceBytes.GetSection(SectionBounds, SourceData);
                    if (CompareByteArray(Section, TargetSection)) return SectionBounds;
                }
            }
            return null;
        }

        /// <summary>
        /// Gets the section.
        /// </summary>
        /// <param name="Bytes">The bytes.</param>
        /// <param name="Area">The area.</param>
        /// <param name="BitmapData">The bitmap data.</param>
        /// <returns></returns>
        private static byte[] GetSection(this byte[] Bytes, Rectangle Area, BitmapData BitmapData)
        {
            using (MemoryStream Out = new MemoryStream())
            {
                for (int Y = Area.Top; Y <= Area.Bottom - 1; Y++)
                {
                    int Offset = (BitmapData.Stride * Y) + (BytesPerPixel * Area.Left);
                    Out.Write(Bytes, Offset, Area.Width * BytesPerPixel);
                }
                return Out.ToArray();
            }
        }

        /// <summary>
        /// Gets the bitmap data.
        /// </summary>
        /// <param name="Bitmap">The bitmap.</param>
        /// <param name="Area">The area.</param>
        /// <param name="BitmapData">The bitmap data.</param>
        /// <returns></returns>
        private static byte[] GetBitmapData(this Bitmap Bitmap, Rectangle Area, ref BitmapData BitmapData)
        {
            BitmapData = Bitmap.LockBits(Area, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
            byte[] Out = new byte[BitmapData.Height * BitmapData.Stride + 1];
            System.Runtime.InteropServices.Marshal.Copy(BitmapData.Scan0, Out, 0, Out.Length);
            Bitmap.UnlockBits(BitmapData);
            return Out;
        }

        /// <summary>
        /// Gets the pixel from array.
        /// </summary>
        /// <param name="Array">The array.</param>
        /// <param name="Point">The point.</param>
        /// <param name="BitmapData">The bitmap data.</param>
        /// <returns></returns>
        private static Color GetPixelFromArray(this byte[] Array, Point Point, BitmapData BitmapData)
        {
            int Offset = (BitmapData.Stride * Point.Y) + (BytesPerPixel * Point.X);
            return Color.FromArgb(Array[Offset + 2], Array[Offset + 2], Array[Offset]);
        }
    }
}
